#include <sstream>
#include <math.h>
#include <glut.h>
#include <fstream>
#include <string>
#include <Windows.h>
#include <iostream>

using namespace std;

const long  DELAY = 30, START_TIME =  GetTickCount64();
const bool two_ways = false;
const int WIDTH = 1000, HEIGHT = 600, LANE_WIDTH = 40, NUMBER_OF_LANES = two_ways? 4 : 2,
	ROAD_WIDTH = LANE_WIDTH * NUMBER_OF_LANES, MARGIN = 10, xCENTER2 = 300, yCENTER2 = 300, RADIUS2 = 300, //center  is bigger circle
	xCENTER = xCENTER2 + 100, yCENTER = yCENTER2, RADIUS = RADIUS2 - 100, // center2 is smaller one.
	NUMBER_OF_FRAMES = 1000 / DELAY;
const GLfloat RADIUS_CORRECTION = 0.5, PI = 3.14159265, DEG2GRAD = PI / 180, CIRCUMFERENCE2 = 2 * PI * RADIUS2, CIRCUMFERENCE = 2 * PI * RADIUS;

class Point{
		public:
			GLfloat x, y, z;
			
			Point()
			{
				x = 0;
				y = 0;
				z = 0;
			}

			Point(GLfloat x,GLfloat y, GLfloat z = 0)
			{
				Point::x = x;
				Point::y = y;
				Point::z = z;
			}

			Point rotateAbout(GLdouble degAngle, Point axis)
			{
				GLdouble radAngle = degAngle * DEG2GRAD;
				GLdouble c = cos(radAngle);
				GLdouble s = sin(radAngle);
				GLdouble t = (1-c);
				GLdouble x = axis.x ;
				GLdouble y = axis.y ;
				GLdouble z = axis.z;
				GLdouble temp[9];
				GLdouble v[3] = {this->x, this->y, this->z};

				temp[0] = t*x*x + c; temp[3] = t*x*y - s*z; temp[6] = t*x*z + s*y;
				temp[1] = t*x*y + s*z; temp[4] = t*y*y + c; temp[7] = t*y*z - s*x;
				temp[2] = t*x*z - s*y; temp[5] = t*y*z + s*x; temp[8] = t*z*z + c;

				this->x = temp[0]*v[0] + temp[3]*v[1] + temp[6]*v[2];
				this->y = temp[1]*v[0] + temp[4]*v[1] + temp[7]*v[2];
				this->z = temp[2]*v[0] + temp[5]*v[1] + temp[8]*v[2];
				return *this;
			}

			Point rotatex(GLfloat rotationangel, bool createNewPoint) {
				GLfloat newx, newy, newz ;
				newx = x;
				newy = cos(rotationangel * DEG2GRAD) * y - sin(rotationangel * DEG2GRAD) * z;
				newz = sin(rotationangel * DEG2GRAD) * y + cos(rotationangel * DEG2GRAD) * z;
				if (createNewPoint) {
					return Point(newx, newy, newz);	
				} else {
					z = newz;
					x = newx;
					y = newy;
					return *this;
				}
			}

			Point rotatey(GLfloat rotationangel, bool createNewPoint) {
				GLfloat newx, newy, newz ;
				newy = y;
				newz = cos(rotationangel * DEG2GRAD) * z - sin(rotationangel * DEG2GRAD) * x;
				newx = sin(rotationangel * DEG2GRAD) * z + cos(rotationangel * DEG2GRAD) * x;
				if (createNewPoint) {
					return Point(newx, newy, newz);	
				} else {
					z = newz;
					x = newx;
					y = newy;
					return *this;
				}
			}

			Point rotatez(GLfloat rotationangel, bool createNewPoint)
			{
				
					GLfloat newx, newy, newz ;
					newz = z;
					newx = cos(rotationangel * DEG2GRAD) * x - sin(rotationangel * DEG2GRAD) * y;
					newy = sin(rotationangel * DEG2GRAD) * x + cos(rotationangel * DEG2GRAD) * y;
				if (createNewPoint) {
					return Point(newx, newy, newz);	
				} else {
					z = newz;
					x = newx;
					y = newy;
					return *this;
				}
			}
			
			Point translate(GLfloat x, GLfloat y, GLfloat z, bool createNewPoint)
			{
				if (createNewPoint)
				{
					return Point(Point::x + x, Point::y + y, Point::z + z);
				} else 
				{
					Point::x += x;
					Point::y += y;
					Point::z += z;
					return *this;
				}
			}
			
			GLdouble magnitude(){
				return sqrt(x * x + y * y + z * z);
			}
			Point normalize(bool createNew){
				if (createNew) {
					Point temp = Point(x,y,z);
					GLdouble magnitude = temp.magnitude();
					temp.x /= magnitude;
					temp.y /= magnitude;
					temp.z /= magnitude;
					return temp;
				} else {
					GLdouble magnitude = Point::magnitude();
					x /= magnitude;
					y /= magnitude;
					z /= magnitude;
					return *this;
				}
			}
			Point scale(GLdouble scale, bool createNew){
				if (createNew) {
					Point temp = Point(x,y,z);
					temp.x *= scale;
					temp.y *= scale;
					temp.z *= scale;
					return temp;
				} else {
					x *= scale;
					y *= scale;
					z *= scale;
					return *this;
				}
			}
		};
	
	class Line {
		public:
			Point * p1, * p2;
			Point * up;
			Line()
			{
			}

			Line(Point * p1, Point * p2, Point * up = NULL)
			{
				Line::p1 = p1;
				Line::p2 = p2;
				Line::up = up;
				
			}

			Point normalize(){
				Point directionVector = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				GLdouble magnitude = directionVector.magnitude();
				directionVector.x /= magnitude;
				directionVector.y /= magnitude;
				directionVector.z /= magnitude;
				return directionVector;
			}

			Point crossProduct(Point u, Point v) {
				//u = ai + bj + ck  and v = di + ej + fk
				//u x v = (bf - ce) i + (cd - af) j + (ae - bd) k
				
				Point UxV;
				UxV.x = (u.y * v.z) - (u.z * v.y);
				UxV.y = (u.z * v.x) - (u.x * v.z);
				UxV.z = (u.x * v.y) - (u.y * v.x);
				return UxV;
			}

			Line translatex(GLfloat xt) {
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				Point u = crossProduct(n, *up);
				u.normalize(false);
				u.scale(xt, false);
				
				(*p1).x += u.x;
				(*p1).y += u.y;
				(*p1).z += u.z;
				
				(*p2).x += u.x;
				(*p2).y += u.y;
				(*p2).z += u.z;

				return *this;
			}

			Line translatey(GLfloat yt) {
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				Point u = crossProduct(n, *up);
				u.normalize(false);
				Point v = crossProduct(u, n);
				v.scale(yt, false);
				
				(*p1).x += v.x;
				(*p1).y += v.y;
				(*p1).z += v.z;
				
				(*p2).x += v.x;
				(*p2).y += v.y;
				(*p2).z += v.z;
				
				return *this;
			}

			Line translatez(GLfloat zt) {
				
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				n.scale(zt, false);
				
				(*p1).x += n.x;
				(*p1).y += n.y;
				(*p1).z += n.z;
				
				(*p2).x += n.x;
				(*p2).y += n.y;
				(*p2).z += n.z;
				
				return *this;
			}

			Line rotateu(GLdouble angel){
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				Point u = crossProduct(n, *up);
				u.normalize(false);
				p2->rotateAbout(angel, u);
				return *this;
			}
			
			Line rotaten(GLdouble angel) {
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				p2->rotateAbout(angel, n);
				return *this;
			}

			Line rotatev(GLdouble angel) {
				Point n = Point((*p2).x - (*p1).x, (*p2).y - (*p1).y , (*p2).z - (*p1).z);
				n.normalize(false);
				Point u = crossProduct(n, *up);
				u.normalize(false);
				Point v = crossProduct(u, n);
				v.normalize(false);
				p2->rotateAbout(angel, v);
				return *this;
			}
	};




class Snake {
public:
	/*
	Contains all the points of all the body pieces.
	*/
	Point * bodyPositions;
	Point translation;
	int score;
	GLfloat xt , yt, zt;
	/*
	size of the snake's body.
	*/
	GLint size, headRotation,highScore;
	GLfloat headSize, bodySize;
	
	/*
	Draws the snake & its body.
	*/
	Snake(){
		size = 15;
		score = 0;
		highScore = 0;
		headSize = 0.5, bodySize = 0.5;
		bodyPositions = new Point[size];
		bodyPositions[0] = Point(1.0, 0, 0);
		headRotation = 90;
		for(int i = 1; i < size ; i++) {
			bodyPositions[i] = Point(headSize + (bodySize * ((i * 2) -1)), 0, 0);
		}
		translation = Point(0, 0, 0);
	}
	void draw() {
		glPushMatrix();
		glColor3f(1, 1, 1);
		glPushMatrix();
		glTranslatef(bodyPositions[0].x, bodyPositions[0].y, bodyPositions[0].z);
		glRotatef(headRotation,0, 0, 1);
		glutSolidSphere(headSize, 30, 30);
		glPopMatrix();
		
		glColor3f(0, 1, 1);
		for (int i = 0; i < size; i++) {
			glPushMatrix();
			glTranslatef(bodyPositions[i].x, bodyPositions[i].y, bodyPositions[i].z);
			glutSolidSphere(bodySize, 30, 30);
			glPopMatrix();
		}

		glPopMatrix();
		//cout << "ousama";
	}
	
	
	/*
	Moves the head in the same direction as before.
	This is the animation function & should be called by the timer
	*/
	void move();
	
	/*
	Moves the head of the snake in the desired direction (RIGHT, DOWN, LEFT, UP).
	This method should be called when the user presses on keyboard to change the sanke's direction
	*/
	
	bool move2(int direction,int giftX,int giftY,Point * myBlocks,bool *done) {
		GLfloat y = bodyPositions[0].y;GLfloat x = bodyPositions[0].x;
		switch(direction) {
		case 'w':headRotation = 0; y += 1;if(y > 20)y=-20;if(y<-20)y=20; break;
		case 's':headRotation = 180; y += -1 ;if(y > 20)y=-20;if(y<-20)y=20; break;
		case 'a':headRotation = 90; x += -1 ;if(x > 20)x=-20;if(x<-20)x=20;break;
		case 'd':headRotation = 270; x += 1 ; if(x > 20)x=-20;if(x<-20)x=20;break;
		}

		for(int i = size -1; i >0;i--)
		{
			bodyPositions[i].x = bodyPositions[i-1].x;
			bodyPositions[i].y = bodyPositions[i-1].y;
		}

		bodyPositions[0].x = x;
		bodyPositions[0].y = y;
		
		for(int i = 1 ; i < size ; i++)
		{
			if(bodyPositions[0].x == bodyPositions[i].x && bodyPositions[0].y == bodyPositions[i].y)
			{
				exit(0);
			}
		}

		bool x1 = didCollide(giftX,giftY,myBlocks,done);
		return x1;
		glutPostRedisplay();
	}

	bool move3(int direction,int giftX,int giftY,Point * myBlocks,bool* done) {
		GLfloat y = bodyPositions[0].y;GLfloat x = bodyPositions[0].x;
		switch(direction) {
		case 'a':if(y == bodyPositions[1].y) //  we will go down/up depending he is going right or left
				 {
					 if(x > bodyPositions[1].x) // we will go up he is going right
					 {
						 y += 1;if(y > 20)y=-20;if(y<-20)y=20; 
					 }else // we will go doiwn he is going left
					 {
						 y -= 1;if(y > 20)y=-20;if(y<-20)y=20; 
					 }

				 }else if(x == bodyPositions[1].x) // we will go left/right depening he is going up or down
				 {
					 if(y > bodyPositions[1].y) // we will go left because he is goinf up
					 {
						 x -= 1 ; if(x > 20)x=-20;if(x<-20)x=20;
					 }else
					 {
						 x += 1 ; if(x > 20)x=-20;if(x<-20)x=20;
					 }
				 }break;
		case 'd':if(y == bodyPositions[1].y) //  we will go down/up depending he is going right or left
				 {
					 if(x > bodyPositions[1].x) // we will go up he is going right
					 {
						 y -= 1;if(y > 20)y=-20;if(y<-20)y=20; 
					 }else // we will go doiwn he is going left
					 {
						 y += 1;if(y > 20)y=-20;if(y<-20)y=20; 
					 }

				 }else if(x == bodyPositions[1].x) // we will go left/right depening he is going up or down
				 {
					 if(y > bodyPositions[1].y) // we will go left because he is goinf up
					 {
						 x += 1 ; if(x > 20)x=-20;if(x<-20)x=20;
					 }else
					 {
						 x -= 1 ; if(x > 20)x=-20;if(x<-20)x=20;
					 }
				 }break;
		}

		for(int i = size -1; i >0;i--)
		{
			bodyPositions[i].x = bodyPositions[i-1].x;
			bodyPositions[i].y = bodyPositions[i-1].y;
		}

		bodyPositions[0].x = x;
		bodyPositions[0].y = y;
		
		for(int i = 1 ; i < size ; i++)
		{
			if(bodyPositions[0].x == bodyPositions[i].x && bodyPositions[0].y == bodyPositions[i].y)
			{
				exit(0);
			}
		}

		bool x1 = didCollide(giftX,giftY,myBlocks,done);
		return x1;
		glutPostRedisplay();
	}
	
	/*
	Detects collision with the food & returns true if it eats the food.
	*/
	bool eatFood();
	
	/*
	Detects collision with the body of the snake & the borders.
	*/
	bool didCollide(int giftX,int giftY,Point *blocks,bool *done)
	{
		bool ret = false;
		if(bodyPositions[0].x >= giftX-1 && bodyPositions[0].x<= giftX+1 && bodyPositions[0].y >= giftY-1 && bodyPositions[0].y<= giftY+1)
		{
			growUp();
			score++;
			ret = true;
		}
		
			for(int i = 0 ; i < 20 ;i++)
			{
				if((bodyPositions[0].x == blocks[i].x && bodyPositions[0].y == blocks[i].y  )||bodyPositions[0].x >= blocks[i].x-0.75 && bodyPositions[0].x<= blocks[i].x+0.75 && bodyPositions[0].y >= blocks[i].y-0.75 && bodyPositions[0].y<= blocks[i].y+0.75)
				{
					score--;
					if(score < 0)
						*done = true;
				}
			
			}
			return ret;
		
	}
	
	/*
	Increases the body of the snake by one piece when it eats food.
	*/
	void growUp()
	{
		
		bodyPositions[size] = Point(bodyPositions[size-1].x + xt, bodyPositions[size-1].y + yt, bodyPositions[size-1].z);
		size++;
		glutPostRedisplay();
	}
};